할당 자원 관리
개요
파드에 사용할 수 있는 자원 중 가장 직관적인 것 중 하나는 프로세스가 실행되는데 필요한 자원일 것이다.
여기에 사용될 수 있는 자원은 대표적으로 cpu, 메모리, 로컬 스토리지 등이 있다.
여기에 커스텀 자원을 넣어서 확장하는 방법도 존재하는데, 이것 추후에 더 정리하겠다.
이 문서에서는 파드에 어떻게 이 리소스들을 할당할 수 있는지 살펴보자.
양식 작성법
파드에서 실제로 작성하게 되는 양식은 이런 식이다.
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
보통은 이렇게 각 컨테이너마다 resources
필드를 이용해 자원에 대한 설정을 넣어준다.
이 스펙들을 명시하는 행위는 kube-scheduler가 파드를 스케줄링할 노드를 고를 때, kubelet이 컨테이너를 실행시킬 때 등 다양하게 활용된다.
요청하는 필드에는 requests
, limits
라는 두 가지 값이 가능하다.
requests는 최소한 이만큼은 컨테이너가 리소스를 받을 수 있어야 한다고 요청하는 필드이다.
limits는 아무리 그래도 이만큼 이상 컨테이너가 리소스를 가져가면 안 된다고 제한하는 필드이다.
그래서 간단하게 최소 얼마, 최대 얼마를 지정하는 거라 생각하면 되겠다.
더 자세하게는 [[#Requests]], [[#Limits]]에서 본다.
각 컨테이너마다 자원에 대한 설정을 하는데 이것들이 각각 가지는 의미가 있긴 하지만 때로는 파드 내 컨테이너의 자원 관련 설정 총합을 활용하는 매커니즘도 동작한다는 걸 유의하자.
파드의 .status
에 사용량이 보고된다.
파드 차원 자원 할당
spec:
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
containers:
- name: app
image: images.my-company.example/app:v4
...
기본 사용 방식은 파드의 컨테이너마다 자원을 할당하는 것이다.
그러나 이런 상황이 있을 수 있다.
- 파드 내 컨테이너들의 각 사용량을 짐작하기 어려울 때.
- 파드 내 컨테이너가 너무 많아서 일일히 쓰기 힘들 때.
- 컨테이너 간 사용량이 들쑥날쑥해서 제한된 범위 내에서 효율적으로 쓰고 싶을 때.
이렇게 하면 파드의 전체 사용량을 따져서 값을 계산하게 된다!
이건 Kubernetes v1.32 - Penelope에서 알파인 기능으로 PodLevelResources
피처 게이트를 활성화해야 사용할 수 있다.
Requests
request는 해당 컨테이너가 이만큼은 사용하길 바란다고 요청하는 필드이다.
그렇다고 사실 메모리 50메가 쓰는 컨테이너에 100메가로 요청한다고 해서 강제적으로 그만큼 메모리가 묶여버리는가?
그건 아니다.
이 값은 실제 컨테이너가 얼마나 자원을 쓰는지에 대해서는 일절 관여하지 않는다.
지금부터 보이는 그림들은 메모리 자원에 대한 상황을 바탕으로 그려졌다.
cpu와 메모리에 대해서 동작하는 방식이 다른데, 이것은 컴퓨팅 자원에서 조금 더 제대로 다룬다.
주의할 것은 실제로 해당 노드가 그만큼의 여유 공간이 확보돼있는지 스케줄러는 상관하지 않는다는 것이다.
위의 예시에서, 파드 1은 사실 500m 만큼의 자원을 쓰고 있다고 쳐보자.
그리고 파드 2는 사실 400m 만큼의 자원을 쓰고 있다.
그렇다면 실제로 해당 노드는 100m 정도만큼의 여유공간만 남아있는 것이다.
파드 3이 실제로 배치되고 나서 자원을 100m 이상 쓰게 된다면, 노드의 실제 가용한 자원을 초과하게 되므로 문제가 발생하게 된다.
이 경우 eviction, OOM kill 등 다양한 자원 정리 해소 매커니즘이 동작하게 될 것이다.
자칫하다간 서비스 장애가 발생할 수 있는 사항이니 세밀하게 제어를 할 필요가 있다.
반대의 상황에서는 운영 비효율이 발생할 수 있다.
파드가 실제로 사용하지도 않을 만큼의 자원을 크게 request해두면 다른 파드들이 배치되기 힘들어진다.
그럼 노드의 자원은 실질적으로 놀게 되는 상황이 발생할 수도 있다.
Limits
limits는 실제로 해당 컨테이너 또는 파드가 사용할 수 있는 최대치를 제한하는 필드이다.
requests와 다르게, 이 필드는 실제 컨테이너가 얼마나 자원을 쓰는지에 대해 관여한다!
참고로, 3번 케이스처럼 eviction이 일어나는 케이스에서는 PriorityClass, QoS가 고려된다.
이 중 QoS는 requests와 limits 설정에 따라 클래스가 결정되기에 값을 잘 설정하는 것이 중요하다!
자원 타입
자원 타입 중 CPU, 메모리가 가장 핵심적이고 운영 상 중요하게 다뤄야 하는 자원들이다.
이에 대한 동작 방식을 아는 게 중요한 관계로 컴퓨팅 자원에 따로 담았다.
huge-page
메모리와 관련된 자원 설정으로, 리눅스에서는 huge page 자원을 명시할 수 있다.
이건 커널에 기본 페이지 사이즈보다 더 큰 메모리 블록을 할당하는 기능이다.
hugepages-2Mi: 80Mi
이런 식으로 쓴다.
기본 페이지가 4Kib에서 이렇게 쓸 수 있다.
만약 40 2MiB huge pages (a total of 80 MiB)만큼을 시도하면 할당은 실패할 것이다.
이건 오버커밋할 수 없다.
이 개념을 아직 제대로 이해했다고 하기 힘들 것 같다.
그래서 문서에서 본 내용만 대충 적어두고 나중에 조금 더 탐구해보려고 한다.
ephemeral-storage
ephemeral-storage는 노드의 디스크 공간을 의미한다.
단순하게 말하면 그냥 클러스터를 운영하면서 발생하는 모든 디스크 공간을 의미한다.
구체적으로는 노드의 다음 공간들을 의미한다.
- emptyDir(메모리 기반 emptyDir은 메모리 자원이다!) 볼륨 마운팅을 통해 확보되는 공간
- 컨테이너의 thin layer의 디스크 공간
- 컨테이너 실행 간 발생하는 로그나 각종 메타데이터에 등 노드 레벨에서 기록되는 공간
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
ephemeral-storage: "2Gi"
limits:
ephemeral-storage: "4Gi"
volumeMounts:
- name: ephemeral
mountPath: "/tmp"
volumes:
- name: ephemeral
emptyDir:
sizeLimit: 500Mi
이리 되면 4기비 중에서 500메비는 emptyDir로 쓰일 것이다.
스토리지에 대한 값은 기본이 바이트라는 걸 참고하자.
kubelet에서 이러한 로컬 스토리지가 사용되는지도 체크해준다.
대체로 이 공간은 /var/lib/kubelet
, /var/logs
이런 곳이 된다.
참고로 이러한 후자의 방법에 대해서는 사용 공간에 대한 제대로 된 추적이 되지 않을 수 있으니 주의하자.
기본적으로 kubelet은 주기적 스캔을 통해 디스크 공간을 추적한다.
참고로 kubelet은 지워진 파일의 디스크립터까지 추적하지는 않는다.
가령 어떤 컨테이너가 emptyDir에 있는 파일을 읽고 있다고 쳐보자.
이 파일을 지우더라도, 파일디스크럽터는 남아있고 메모리에 데이터가 남아있으니 컨테이너는 파일 내용물을 볼 수 있는 상태이다.
그래도 이걸 kubelet이 스토리지가 비워지지 않았다고 치지 않는다는 것이다.
이걸 활용할 케이스가 있을진 모르겠다.
Kubernetes v1.31 - Elli 기준 베타인 기능으로, 파일시스템 프로젝트 쿼타라는 방식으로 디스크 사용량 스캔을 하는 방법도 있다고 한다.
이게 뭔지는 잘 모르겠어서 간단하게 지피티의 도움을 받았다.
간단하게 번역한 내용만 남겨두고, 나중에 조금 더 파헤쳐봐야겠다.
- 파일시스템 프로젝트 쿼타
- 이거 os 레벨의 기능이다.
- xfs, ext4fs에서 지원한다.
- 근데 이건 그냥 모니터링하는 기능이고, 그 자체가 리밋을 지정하진 않는다.
- 쿠버는
1048576
pid로 시작하며, 이는/etc/projects
와/etc/projid
에 저장된다. - 여기에 들어가는 id는 쿠버가 쓰지 않는다.
- 쿼타는 디렉 스캐닝보다 빠르고 정확하다.
- 한 디렉이 플젝에 할당되면, 커널은 해당 디렉에 만들어진 모든 파일에 대한 블록수를 체크하면 된다.
- 쿼타는 지워졌지만 아직 열린 파일에 대해서도 정확하게 감시한다.
- 파드에 쿼타를 쓰려면, 파드가 유저 네임스페이스에 있어야 한다.
- 커널은 쿼타에 계산될 스토리지 메트릭을 보장하며 파일시스템의 projectId의 변화를 제한할 것이다.
이 쿼타를 쓰려면?
- kubelet에 LocalStorageCapacityIsolationFSQuotaMonitoring=true 피쳐게이트
- UserNamespacesSupport 피처게이트 돼야 하고, 커널과 cri가 유저 네임스페이스를 지원해야 한다.
- root 파일시스템(이나 런타임 파일시스템)에서 이걸 지원해야 한다.
- xfs는 전부 지원하고, ext4에서는
sudo tune2fs -O project -Q prjquota /dev/block-device
이런 식으로 마운팅되기 전에 먼저 지정해야 한다. - 마운팅 옵션은
prjquota
이다.
하기 싫다면?
위의 피쳐게이트를 없애면 된다.
limits 초과시
requests에 대한 동작은 전부 결국 같으므로 구태여 설명하지 않겠다.
만약 limits를 넘는다면 파드는 축출된다.
당연히 파드 안의 한 컨테이너가 이 제한을 넘겨도 파드째로 축출된다.
이 상황은 당연히 kubelet이 노드 스토리지에 대한 측정을 하고 있다는 것을 전제로 한다.
그런데 로컬 스토리지에 대한 리소스 관련 설정이 아예 없는 파드들로만 이뤄진 상황이라면?
이럴 경우에는 별도로 kubelet이 컨테이너나 파드별로 사용량을 측정하지 않으니 축출은 진행되지 않는다.
다만 그러다가 결국 노드 디스크 공간이 부족해져버린다면 최소한 노드에 NoExecute 테인트, 톨러레이션를 걸어버린다.
그러면 이에 대한 테인트, 톨러레이션 대비가 안 된 파드들은 전부 튕겨저 나가버릴 것이다.
초신성 ㄷㄷ
확장 리소스
resources:
limits:
hardware-vendor.example/foo: 2
확장 자원은 kubernetes.io
도메인 바깥의 리소스를 말한다.
이건 쿠버에 내장되지 않은 자원을 넣을 수 있도록 해준다.
클러스터로 GPU 자원을 운용할 때는 이런 방식을 사용한다.
이런 리소스를 사용하기 위해서는 두가지 과정이 필요하다.
일단 관리자가 확장 자원을 게시해야 하고, 그리고 개발자가 파드에서 이 자원을 요청해야 한다.
이 값은 위의 것들과 같이 사용된다.
사용 가능한 양에 대해서는 스케줄러가 파악을 해준다.
유의점
확장 자원은 cpu나 메모리처럼 적당히 값을 잘라서 쓰고 하는 개념이 지원되지 않는다.
그래서 무조건 정수 값으로 떨어지게 설정해야 한다는 것에 유의하자.
또한 확장 자원은 오버커밋(실제 사용 가능한 양보다 많게 사용되는 상황[1])될 수 없다.
그래서 항상 requests와 limits를 동일하게 설정해야 한다.
그럼 이러한 확장 자원을 어떻게 등록하면 될까?
자원의 유형에 따라 노드 레벨과 클러스터 레벨로 두 층위를 또 나뉜다.
노드 레벨 자원 확장
가장 기본적인 방법은 직접 api서버에 PATCH
요청을 날려서 리소스를 등록하는 것이다.
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
http://k8s-master:8080/api/v1/nodes/k8s-node-1/status
이런 식으로 하면 노드의 capacity에 해당 값이 추가된다.
참고로 여기에서 ~1
은 /
을 인코딩한 건데, json 방식에서 /
을 문자열로 취급하고자 할 때 쓴다.
그래서 example.com/foo
가 확장된 자원으로 들어가게 된다.
{
"status": {
"capacity": {
"example.com/foo": "5"
}
}
}
이 방식은 그게 실제 사용되는 리소스던 뭐던 임의의 자원을 넣어주는 것을 가능하게 한다.
예를 들면 한 노드에 무조건 파드가 3개만 띄워지게 하고 싶다.
그럴 때 임의의 자원을 3개로 등록을 한 다음에 파드에 이 값을 requests 해버리면 딱 파드 3개만 배치하는 식으로 운용할 수 있다!
(좋은 방식인지는 모르겠지만, 뭐 이런 식으로도 활용할 수 있다 정도..?)
이 방식은 비동기적으로 적용되기 때문에, 바로 노드의 상태값에 반영이 안 될 수 있다.
아니면 GPU, 인피니밴드 등의 실제 노드에 있는 자원을 적절하게 등록해서 사용하는 방법도 있다.
디바이스 플러그인으로, kubelet에서 이를 인식할 수 있도록 직접 구현해서 넣어주면 된다.
관련한 내용은 이 문서[2]를 참고한다.
참고로 kubectl에서 노드에 patch를 하는 방식으로 적용하는 것도 가능하다.
이때 --subresource=status
라는 인자를 넣어주지 않으면, 기본적으로 사용자의 status 변경사항은 무시되기 때문에 유의해야 한다.
클러스터 레벨 자원 확장
이건 노드에 묶이지 않는 자원을 관리할 때 사용하는 방식이다.
스케줄러의 기능을 확장하는 툴이나 프로그램에 의해 관리된다.
이 확장자에 대한 정보를 스케줄러 설정에 넣어주면, 스케줄러가 관련 리소스를 요청하는 파드를 스케줄링할 때 해당 확장자에게 스케줄링을 위임해버린다.
설정법은 스케줄러 관련 설정을 참고하자[3].
{
"kind": "Policy",
"apiVersion": "v1",
"extenders": [
{
"urlPrefix":"<extender-endpoint>",
"bindVerb": "bind",
"managedResources": [
{
"name": "example.com/foo",
"ignoredByScheduler": true
}
]
}
]
}
이렇게 하면 파드 리퀘스트에 example.com/foo
가 있을 때 스케줄러에서 스케줄러 확장자로 보내버린다.
ignore 필드 부분을 넣어야 스케줄러가 PodFitResources
부분 검증을 하지 않는다.
PID
프로세스 ID 개수를 제한하는 것이 가능하다.
다만 이건 resource 필드에 요청을 하거나 제한을 걸 수 있는 것은 아니라, 순수 관리자의 영역이라 볼 수 있다.
kubelet 설정으로 넣어주는 식으로 설정하는데, 자세한 건 이 문서[4] 참고.
관련 문서
이름 | noteType | created |
---|---|---|
ConfigMap | knowledge | 2025-01-12 |
Secret | knowledge | 2025-01-12 |
할당 자원 관리 | knowledge | 2025-01-12 |
컴퓨팅 자원 | knowledge | 2025-03-09 |
Downward API | knowledge | 2025-03-09 |
RuntimeClass | knowledge | 2025-03-19 |
Dynamic Resource Allocation | knowledge | 2025-04-18 |
E-emptyDir 제한 | topic/explain | 2025-01-16 |
E-projected 볼륨 - 동적 업데이트, 중복 활용 | topic/explain | 2025-03-10 |
T-마운트 전파 Bidirectioal | topic/temp | 2025-02-28 |